package IProject;

use Set;
use Signature;
use CHeaderParser; 
use IProvider;
use IModule;
use IModuleProvider;
use SimpleCreatorFacotry;
use TinyMessage;
use Graph;

sub new {
	my $class = shift;
	
	my $self = {
		modules => [],
		classes => new Set()
	};
	
	bless $self, $class;
	
	return $self;
}

sub loadCxFile {
	return (shift)->{context}->{file};
}

sub loadCxModule {
	my ($this, $ccontext) = @_;
	my $context = $this->{context};
	my $file = $context->{file};
	
	if (!exists $context->{module}) {
		fatal 'F_INTERNAL', "CContext not set" if (!$ccontext);
		
		my $moduleClass = $ccontext->classContext();
		fatal 'F_INTERNAL', "CContext is not valid: unable to found module class in $file" if (!$moduleClass); 
		
		my $module = new IModule();
		$module->setClass($moduleClass);
		
		$context->{module} = $module;
		push(@{$this->{modules}}, $module);	
	}
	
	return $context->{module};
}

sub load {
	my ($this, $cproject) = @_;
	
	$cproject->load(sub {
		$this->{context} = { file => shift };
		
		CHeaderParser::parseFile($this->loadCxFile(), {
				class => sub {
					my $context = shift;
					my $class = $context->classContext();
					$this->{classes}->put($class);
				},
				
				bind_singleton => sub {
					my ($context, $class, $impl, $method) = @_;
					my $provider = new IProvider($class, $impl, $method, 's');
					$this->loadCxModule($context)->addProvider($provider);
				},
				
				bind_direct => sub {
					my ($context, $class, $impl, $method) = @_;
					my $provider = new IProvider($class, $impl, $method, 'd');
					$this->loadCxModule($context)->addProvider($provider);
				},
				
				bind_custom => sub {
					my ($context, $class, $impl, $method) = @_;
					my $provider = new IProvider($class, $impl, $method, 'c');
					$this->loadCxModule($context)->addProvider($provider);
				},
				
				sub_module => sub {
					my ($context, $module_sig, $method) = @_;
					my $provider = new IModuleProvider($module_sig, $method);
					$this->loadCxModule($context)->addModuleProvider($provider);
				},
				
				ioc => sub {
					my ($context, $module, $variable) = @_;
					$this->loadCxModule($context)->setIOCVariable($variable);
				}
		});
	});
	
	my $creatorFactory = new SimpleCreatorFacotry();

	# linking project with modules
	grep { $_->setProject($this) } @ { $this->{modules} }; 

	my @orderedModules = ();

	# creating project graph
	{
		my $graph = Graph->new;

		# creating vertexes
		grep { $graph->add_vertex(signature($_->class())) } @ { $this->{modules} };

		# creating dependencies
		foreach my $module (@ { $this->{modules} }) {
			my @sub = map { $_->{module} } $module->subModules();
			foreach (@sub) {
				$graph->add_edge(signature($module->class()), signature($_->class()));
			}
		}
		
		info "Modules graph: $graph";
		
		#check for cycle
		my @has_cycle = $graph->find_a_cycle();
		fatal 'F_PROJECT_HAS_CYCLE', @has_cycle if (@has_cycle);
		
		#topological sorting
		my @creation_order = reverse $graph->topological_sort();
		info "Creation order is: ", join(',', @creation_order);
		
		@orderedModules = map { $this->module($_) } @creation_order;
	}
	
	# begin
	grep { $_->begin($this) } @orderedModules;
	
	#spawing
	grep { $_->spawnCreators($creatorFactory) }	@orderedModules;
		
	#commit
	grep { $_->commit() } @orderedModules;
	
}

sub modules {
	return (shift)->{modules};
}

sub module {
	my ($this, $class) = @_;
	
	foreach my $module (@{ $this->modules() })	{
		if (sigs_eq($class, $module->class())) {
			return $module;
		}
	}
	
	return undef;
}

sub classes {
	return (shift)->{classes};
}

1;